
from yacf.Frontend.Shortcuts import getCurrentLanguageAst
c_ast = getCurrentLanguageAst()

from yacf.Tools.Tree import InsertTool, RemoveTool, ReplaceTool,\
    getContainerAttribute, getFileNode, NotValidParent

from yacf.Backends.Common.Visitors.GenericVisitors import FilterVisitor, IDFilter, FuncCallFilter, FuncDeclOfNameFilter, AttributeFilter, FilterVisitor, NodeNotFound


from yacf.Backends.Common.Mutators.AbstractMutator import AbstractMutator, IgnoreMutationException, AbortMutationException
from yacf.Frontend.InternalRepr import AstToIR
from Backends.Cuda.Writers import CUDAWriter


class RemoveAttributeMutator(AbstractMutator):
    """ Remove the child of the first appearance of an attribute inside a node """
    def __init__(self, attr):
        self.attr = attr
 
    def filter(self, ast):
        af = AttributeFilter(match_attribute = self.attr)
        attr_node = af.apply(ast)
        return [attr_node, af.parentOfMatch()]

    def mutatorFunction(self, ast, parent):
        del ast.init
        ast.init = None
        return ast

    def apply(self, ast):
        """ Apply the mutation """
        start_node = None
        try:
            [start_node, parent] = self.filter(ast)
            self.mutatorFunction(start_node, parent)
        except NodeNotFound as nf:
            print str(nf)
        return start_node

class DeclsToParamsMutator(AbstractMutator):
    """ DeclsToParams """ 
    def __init__(self):
        " Save the params "
        # self.params = self.convert(decls);
        pass

    def convert(self, node):
        """ Transform a type declaration to a parameter declaration """
        # TODO: Implement this !
        # If ArrayDecl, change to PointerDecl DONE
        # If StructDecl, change to PointerDecl
        # Else, do not touch
        # Remove initialization DONE
        params_tmp = []
        decls = node.params
        for decl in decls:
            new_decl = RemoveAttributeMutator('init').apply(decl);
            if isinstance(new_decl.type, c_ast.ArrayDecl) or isinstance(new_decl.type, c_ast.Struct):
                PointerMutator().apply(new_decl)
            params_tmp.append(new_decl) 

        return c_ast.ParamList(params = params_tmp, coord = 0, parent = node.parent)

    def filter(self, ast):
        """ Filter definition
            Returns the first node matching with the filter
        """
        # Build a visitor , matching the ParamList node of the AST
        f = FilterVisitor(match_node_type = c_ast.ParamList)
        node = f.apply(ast)
        return node

    def mutatorFunction(self, ast):
        """ Mutator code """
#        InsertTool(subtree = self.params, position = "end").apply(ast, 'params')
        # ReplaceTool(new_node = self.params, old_node = self.params).apply(ast, 'params')
        ast.params = self.convert(ast).params


class IDNameMutator(AbstractMutator):
    """  Replace and ID name with another ID name """
    def __init__(self, old, new):
        self.old = old
        self.new = new
 
    def filter(self, ast):
        id_node = None
        af = IDFilter(id = self.old)
        id_node = af.apply(ast)
        return id_node

    def filter_iterator(self, ast):
        id_node = None
        af = IDFilter(id = self.old)
        # print " IDNameMutator iterator ast: " + str(ast) + "Looking for : " + str(self.old.name)
        visited = []
        try:
            for id_node in af.dfs_iter(ast):
                # print "ID Node : " + str(id_node) + " id: " + str(id(id_node))
                # Avoid recursion
                if not id(id_node) in visited:
                    yield id_node   
                    visited.append(id(id_node))
                else:
                    #~ print "Already Visited"
                    pass
                    # visited.append(id(id_node))
            else:
                # Otherwise , raise exception and stop
                raise StopIteration
        except NodeNotFound:
            raise StopIteration

        print " You shouldn't see this " 


    def mutatorFunction(self, ast):
        if hasattr(ast, 'name'):
            delattr(ast, 'name')
            setattr(ast, 'name', self.new.name)
        elif hasattr(ast, 'declname'):
            delattr(ast, 'declname')
            setattr(ast, 'declname', self.new.name)
        else:
            from yacf.Backends.Common.Mutators.AbstractMutator import MutatorException
            raise MutatorException("Could not apply IDNameMutator, subtree was : " + str(ast))
        return ast




class PointerMutator(AbstractMutator):
    """ Changes the declaration of a variable to be a pointer """
    def filter(self, ast):
        return ast

    def mutatorFunction(self, ast):
            
        if (isinstance(ast.parent, c_ast.Decl) 
            or isinstance(ast.parent, c_ast.PtrDecl) 
            or isinstance(ast.parent, c_ast.ArrayDecl) 
            or isinstance(ast.parent, c_ast.Struct)):
            
                if not isinstance(ast.type, c_ast.ArrayDecl):
                        pointer_node = c_ast.PtrDecl(type = ast.type, quals = [], parent = ast)
                        ast.type.parent = pointer_node
                        ast.type = pointer_node
                else:
                        pointer_node = c_ast.PtrDecl(type = ast.type.type, quals = [], parent = ast)
                        ast.type.parent = pointer_node
                        ast.type = pointer_node            
            
        else:
            pointer_node = c_ast.UnaryOp(op = '*', expr = ast, parent = ast.parent) 
            try:
                attr = getContainerAttribute(ast)
                setattr(ast.parent, attr, pointer_node)
            except NotValidParent:
                # Not having a valid parent here does not means that something 
                #   is wrong. Maybe it is just a temporal node, part of a translation, whatever
                pass
            ast.parent = pointer_node
        return pointer_node


class ReferenceMutator(AbstractMutator):
    """ Changes the declaration of a variable to be a pointer """
    def filter(self, ast):
        return ast

    def mutatorFunction(self, ast):
    
        if not isinstance(ast.type, c_ast.ArrayDecl):
            pointer_node = c_ast.RefDecl(type = ast.type, quals = [], parent = ast)
            ast.type.parent = pointer_node
            ast.type = pointer_node
        else:
            pointer_node = c_ast.RefDecl(type = ast.type.type, quals = [], parent = ast)
            ast.type.parent = pointer_node
            ast.type = pointer_node

        return ast





